package com.raft.core.log;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Set;

import javax.annotation.concurrent.NotThreadSafe;

import com.google.common.eventbus.EventBus;
import com.raft.core.log.entry.Entry;
import com.raft.core.log.entry.EntryMeta;
import com.raft.core.log.sequence.EntrySequence;
import com.raft.core.log.sequence.FileEntrySequence;
import com.raft.core.log.snapshot.EmptySnapshot;
import com.raft.core.log.snapshot.FileSnapshot;
import com.raft.core.log.snapshot.FileSnapshotBuilder;
import com.raft.core.log.snapshot.FileSnapshotWriter;
import com.raft.core.log.snapshot.Snapshot;
import com.raft.core.log.snapshot.SnapshotBuilder;
import com.raft.core.node.NodeEndpoint;
import com.raft.core.rpc.message.InstallSnapshotRpc;

@NotThreadSafe
public class FileLog extends AbstractLog {

    private final RootDir rootDir;

    public FileLog(File baseDir, EventBus eventBus) {
        super(eventBus);
        rootDir = new RootDir(baseDir);

        // 获取最新的日志代
        LogGeneration latestGeneration = rootDir.getLatestGeneration();
        snapshot = new EmptySnapshot();
        // TODO add log
        if (latestGeneration != null) {
        	// 日志存在
            if (latestGeneration.getSnapshotFile().exists()) {
                snapshot = new FileSnapshot(latestGeneration);
            }
            FileEntrySequence fileEntrySequence = new FileEntrySequence(latestGeneration, snapshot.getLastIncludedIndex() + 1);
            commitIndex = fileEntrySequence.getCommitIndex();
            entrySequence = fileEntrySequence;
            // TODO apply last group config entry
            groupConfigEntryList = entrySequence.buildGroupConfigEntryList();
        } else {
        	// 日志不存在
            LogGeneration firstGeneration = rootDir.createFirstGeneration();
            entrySequence = new FileEntrySequence(firstGeneration, 1);
        }
    }

    @Override
    protected Snapshot generateSnapshot(EntryMeta lastAppliedEntryMeta, Set<NodeEndpoint> groupConfig) {
        LogDir logDir = rootDir.getLogDirForGenerating();
        try (FileSnapshotWriter snapshotWriter = new FileSnapshotWriter(
                logDir.getSnapshotFile(), lastAppliedEntryMeta.getIndex(), lastAppliedEntryMeta.getTerm(), groupConfig)) {
            stateMachine.generateSnapshot(snapshotWriter.getOutput());
        } catch (IOException e) {
            throw new LogException("failed to generate snapshot", e);
        }
        return new FileSnapshot(logDir);
    }

    @Override
    protected SnapshotBuilder newSnapshotBuilder(InstallSnapshotRpc firstRpc) {
        return new FileSnapshotBuilder(firstRpc, rootDir.getLogDirForInstalling());
    }

    @Override
    protected void replaceSnapshot(Snapshot newSnapshot) {
        FileSnapshot fileSnapshot = (FileSnapshot) newSnapshot;
        int lastIncludedIndex = fileSnapshot.getLastIncludedIndex();
        int logIndexOffset = lastIncludedIndex + 1;

        // 取剩下的日志
        List<Entry> remainingEntries = entrySequence.subView(logIndexOffset);
        // 写入日志快照所在的目录
        EntrySequence newEntrySequence = new FileEntrySequence(fileSnapshot.getLogDir(), logIndexOffset);
        newEntrySequence.append(remainingEntries);
        newEntrySequence.commit(Math.max(commitIndex, lastIncludedIndex));
        newEntrySequence.close();
        // 关闭现有的日志快照、日志等文件
        snapshot.close();
        entrySequence.close();
        newSnapshot.close();

        // 重命名
        LogDir generation = rootDir.rename(fileSnapshot.getLogDir(), lastIncludedIndex);
        // 读取日志快照和日志
        snapshot = new FileSnapshot(generation);
        entrySequence = new FileEntrySequence(generation, logIndexOffset);
        // 从日志序列中重建集群配置日志链表
        groupConfigEntryList = entrySequence.buildGroupConfigEntryList();
        commitIndex = entrySequence.getCommitIndex();
    }

}
